classdef ALMCloudService < handle
    %ALMCLOUDSERVICE ALM云服务业务逻辑处理类（底层通信层）
    % 负责处理与ALM服务器的所有通信和数据格式转换
    % 遵循DO-178架构分离原则，独立于GUI实现
    %
    % 🏗️ 架构说明（2025-10-04更新）：
    % - 本类是底层HTTP通信层，负责与ALM服务器的直接交互
    % - 对于GUI应用，建议使用 ALMIntegrationService 替代直接使用本类
    % - ALMIntegrationService 提供更高层的API和额外功能：
    %   • 数据验证和权限检查
    %   • 缓存管理
    %   • 与其他Service的协调（PR/CR导出导入等）
    %
    % 使用建议：
    %   GUI代码: 使用 ALMIntegrationService
    %   命令行脚本/工具: 可以直接使用 ALMCloudService
    %   新功能开发: 在 ALMIntegrationService 层添加
    
    properties (Access = private)
        ServerURL char
        APIKey char
        TimeoutSeconds double
        DebugMode logical = false  % 调试模式标志，控制日志输出
    end
    
    methods (Access = public)
        function obj = ALMCloudService(varargin)
            % 构造函数
            % 可选参数：ALMCloudService('ServerURL', url, 'APIKey', key, 'DebugMode', true/false)
            
            % 从配置文件加载默认值
            obj.loadConfigFromFile();
            
            % 解析输入参数（覆盖配置文件中的值）
            if nargin > 0
                for i = 1:2:nargin
                    if strcmpi(varargin{i}, 'ServerURL')
                        obj.ServerURL = varargin{i+1};
                    elseif strcmpi(varargin{i}, 'APIKey')
                        obj.APIKey = varargin{i+1};
                    elseif strcmpi(varargin{i}, 'Timeout')
                        obj.TimeoutSeconds = varargin{i+1};
                    elseif strcmpi(varargin{i}, 'DebugMode')
                        obj.DebugMode = varargin{i+1};
                    end
                end
            end
            
            obj.debugLog('ALM云服务已初始化 - 服务器: %s\n', obj.ServerURL);
        end
        
        function [success, result] = submitPR(obj, prData)
            % 提交PR到ALM服务器
            % 输入：prData - PR数据结构体
            % 输出：success - 提交成功标志, result - 服务器响应结果
            
            success = false;
            result = struct();
            
            try
                % 转换为ALM服务器格式
                serverData = obj.convertToServerFormat(prData);
                
                % 验证数据完整性
                if ~obj.validatePRData(serverData)
                    result.error = 'PR数据验证失败';
                    return;
                end
                
                % 发送HTTP请求
                obj.debugLog('正在提交PR到ALM服务器: %s\n', serverData.issueNumber);
                
                % 调试输出：显示完整的JSON数据
                obj.debugLog('\n=== DEBUG: 发送给ALM服务器的完整JSON数据 ===\n');
                jsonData = obj.convertStructToJSON(serverData);
                obj.debugLog('%s\n', jsonData);
                obj.debugLog('=== DEBUG: JSON数据结束 ===\n\n');
                
                response = obj.sendHTTPRequest('POST', '/pr', serverData);
                
                if ~isempty(response) && response.StatusCode == 201
                    % 提交成功
                    success = true;
                    result.pr_id = response.Body.Data.data.id;
                    result.issueNumber = serverData.issueNumber;
                    result.submitTime = char(datetime('now'));
                    result.message = sprintf('PR提交成功！服务器ID: %d', result.pr_id);
                    
                    fprintf('✅ PR提交成功，服务器ID: %d, 编号: %s\n', result.pr_id, serverData.issueNumber);
                    
                else
                    % 提交失败
                    result.error = obj.parseErrorResponse(response, 'PR');
                    fprintf('❌ PR提交失败: %s\n', result.error);
                end
                
            catch ME
                result.error = sprintf('PR提交异常: %s', ME.message);
                fprintf('❌ %s\n', result.error);
                
                % 增强错误信息
                if contains(ME.message, 'Connection refused') || contains(ME.message, 'timeout')
                    result.error = sprintf('%s\n\n请确认：\n• ALM服务器正在运行\n• 服务器地址正确: %s\n• 网络连接正常', ...
                        result.error, obj.ServerURL);
                end
            end
        end
        
        function [success, result] = submitCR(obj, crData)
            % 提交CR到ALM服务器
            % 输入：crData - CR数据结构体
            % 输出：success - 提交成功标志, result - 服务器响应结果
            
            success = false;
            result = struct();
            
            try
                % 转换为ALM服务器格式
                serverData = obj.convertToServerFormat(crData);
                
                % 验证CR数据完整性
                if ~obj.validateCRData(serverData)
                    result.error = 'CR数据验证失败';
                    return;
                end
                
                % 发送HTTP请求
                fprintf('正在提交CR到ALM服务器: %s\n', serverData.ticketNumber);
                
                % 调试输出：显示完整的JSON数据
                fprintf('\n=== DEBUG: 发送给ALM服务器的完整JSON数据 ===\n');
                jsonData = obj.convertStructToJSON(serverData);
                fprintf('%s\n', jsonData);
                fprintf('=== DEBUG: JSON数据结束 ===\n\n');
                
                response = obj.sendHTTPRequest('POST', '/cr', serverData);
                
                if ~isempty(response) && response.StatusCode == 201
                    success = true;
                    result.cr_id = response.Body.Data.data.id;
                    result.issueNumber = serverData.ticketNumber;
                    result.submitTime = char(datetime('now'));
                    result.message = sprintf('CR提交成功！服务器ID: %d', result.cr_id);
                    
                    fprintf('✅ CR提交成功，服务器ID: %d, 编号: %s\n', result.cr_id, serverData.ticketNumber);
                else
                    result.error = obj.parseErrorResponse(response, 'CR');
                    fprintf('❌ CR提交失败: %s\n', result.error);
                end
                
            catch ME
                result.error = sprintf('CR提交异常: %s', ME.message);
                fprintf('❌ %s\n', result.error);
                
                % 增强错误信息
                if contains(ME.message, 'Connection refused') || contains(ME.message, 'timeout')
                    result.error = sprintf('%s\n\n请确认：\n• ALM服务器正在运行\n• 服务器地址正确: %s\n• 网络连接正常', ...
                        result.error, obj.ServerURL);
                end
            end
        end
        
        function [success, response] = approvePR(obj, prId, approvalStatus, approvalComment)
            % 审批PR - API v2.0规格
            % 基于DO-178规则第2条：明确错误处理，不使用fallback机制
            % 输入：
            %   prId - PR的ID（数据库ID）
            %   approvalStatus - 审批状态：'APPROVED' 或 'REJECTED'
            %   approvalComment - 审批意见（必填）
            % 输出：
            %   success - 审批成功标志
            %   response - 服务器响应结果
            
            success = false;
            response = struct();
            
            try
                % ========== 验证输入参数 ==========
                if ~isnumeric(prId) || prId <= 0
                    response.error = sprintf('❌ PR ID必须为正整数，当前值: %s', string(prId));
                    fprintf('%s\n', response.error);
                    return;
                end
                
                % 验证审批状态（只允许APPROVED或REJECTED）
                validStatuses = {'APPROVED', 'REJECTED'};
                if ~ismember(approvalStatus, validStatuses)
                    response.error = sprintf('❌ 审批状态必须为 APPROVED 或 REJECTED，当前值: %s', approvalStatus);
                    fprintf('%s\n', response.error);
                    return;
                end
                
                % 验证审批意见（必填）
                % 处理cell array情况（uitextarea返回cell array）
                if iscell(approvalComment)
                    approvalComment = strjoin(approvalComment, '\n');
                elseif ~ischar(approvalComment) && ~isstring(approvalComment)
                    approvalComment = string(approvalComment);
                end
                approvalComment = strtrim(approvalComment);
                
                if isempty(approvalComment)
                    response.error = '❌ 审批意见不能为空';
                    fprintf('%s\n', response.error);
                    return;
                end
                
                % ========== 准备请求数据 ==========
                requestData = struct();
                requestData.approval_status = approvalStatus;
                requestData.approval_comment = char(approvalComment);  % 确保是字符串
                
                % 发送HTTP请求
                fprintf('正在提交PR #%d 审批，状态: %s\n', prId, approvalStatus);
                
                % 调试输出
                fprintf('\n=== DEBUG: PR审批请求数据 ===\n');
                jsonData = obj.convertStructToJSON(requestData);
                fprintf('%s\n', jsonData);
                fprintf('=== DEBUG: PR审批请求结束 ===\n\n');
                
                apiPath = sprintf('/pr/%d/approve', prId);
                httpResponse = obj.sendHTTPRequest('PUT', apiPath, requestData);
                
                if ~isempty(httpResponse) && httpResponse.StatusCode == 200
                    % 审批成功
                    success = true;
                    
                    % 解析响应数据
                    if isfield(httpResponse.Body.Data, 'message')
                        response.message = httpResponse.Body.Data.message;
                    end
                    
                    if isfield(httpResponse.Body.Data, 'data')
                        response.data = httpResponse.Body.Data.data;
                    end
                    
                    fprintf('✅ PR #%d 审批成功：%s\n', prId, approvalStatus);
                else
                    % 审批失败
                    response.error = obj.parseErrorResponse(httpResponse, 'PR审批');
                    fprintf('❌ PR #%d 审批失败: %s\n', prId, response.error);
                end
                
            catch ME
                response.error = sprintf('PR审批异常: %s', ME.message);
                fprintf('❌ %s\n', response.error);
                
                % 增强错误信息
                if contains(ME.message, 'Connection refused') || contains(ME.message, 'timeout')
                    response.error = sprintf('%s\n\n请确认：\n• ALM服务器正在运行\n• 服务器地址正确: %s\n• 网络连接正常', ...
                        response.error, obj.ServerURL);
                end
            end
        end
        
        function [success, response] = approveCR(obj, crId, approvalStatus, approvalComment)
            % 审批CR - API v2.0规格
            % 基于DO-178规则第2条：明确错误处理，不使用fallback机制
            % 输入：
            %   crId - CR的ID（数据库ID）
            %   approvalStatus - 审批状态：'APPROVED' 或 'REJECTED'
            %   approvalComment - 审批意见（必填）
            % 输出：
            %   success - 审批成功标志
            %   response - 服务器响应结果
            
            success = false;
            response = struct();
            
            try
                % ========== 验证输入参数 ==========
                if ~isnumeric(crId) || crId <= 0
                    response.error = sprintf('❌ CR ID必须为正整数，当前值: %s', string(crId));
                    fprintf('%s\n', response.error);
                    return;
                end
                
                % 验证审批状态（只允许APPROVED或REJECTED）
                validStatuses = {'APPROVED', 'REJECTED'};
                if ~ismember(approvalStatus, validStatuses)
                    response.error = sprintf('❌ 审批状态必须为 APPROVED 或 REJECTED，当前值: %s', approvalStatus);
                    fprintf('%s\n', response.error);
                    return;
                end
                
                % 验证审批意见（必填）
                % 处理cell array情况（uitextarea返回cell array）
                if iscell(approvalComment)
                    approvalComment = strjoin(approvalComment, '\n');
                elseif ~ischar(approvalComment) && ~isstring(approvalComment)
                    approvalComment = string(approvalComment);
                end
                approvalComment = strtrim(approvalComment);
                
                if isempty(approvalComment)
                    response.error = '❌ 审批意见不能为空';
                    fprintf('%s\n', response.error);
                    return;
                end
                
                % ========== 准备请求数据 ==========
                requestData = struct();
                requestData.approval_status = approvalStatus;
                requestData.approval_comment = char(approvalComment);  % 确保是字符串
                
                % 发送HTTP请求
                fprintf('正在提交CR #%d 审批，状态: %s\n', crId, approvalStatus);
                
                % 调试输出
                fprintf('\n=== DEBUG: CR审批请求数据 ===\n');
                jsonData = obj.convertStructToJSON(requestData);
                fprintf('%s\n', jsonData);
                fprintf('=== DEBUG: CR审批请求结束 ===\n\n');
                
                apiPath = sprintf('/cr/%d/approve', crId);
                httpResponse = obj.sendHTTPRequest('PUT', apiPath, requestData);
                
                if ~isempty(httpResponse) && httpResponse.StatusCode == 200
                    % 审批成功
                    success = true;
                    
                    % 解析响应数据
                    if isfield(httpResponse.Body.Data, 'message')
                        response.message = httpResponse.Body.Data.message;
                    end
                    
                    if isfield(httpResponse.Body.Data, 'data')
                        response.data = httpResponse.Body.Data.data;
                    end
                    
                    fprintf('✅ CR #%d 审批成功：%s\n', crId, approvalStatus);
                else
                    % 审批失败
                    response.error = obj.parseErrorResponse(httpResponse, 'CR审批');
                    fprintf('❌ CR #%d 审批失败: %s\n', crId, response.error);
                end
                
            catch ME
                response.error = sprintf('CR审批异常: %s', ME.message);
                fprintf('❌ %s\n', response.error);
                
                % 增强错误信息
                if contains(ME.message, 'Connection refused') || contains(ME.message, 'timeout')
                    response.error = sprintf('%s\n\n请确认：\n• ALM服务器正在运行\n• 服务器地址正确: %s\n• 网络连接正常', ...
                        response.error, obj.ServerURL);
                end
            end
        end
        
        function [success, data] = queryPRList(obj)
            % 查询PR列表
            % 输出：success - 查询成功标志, data - PR列表数据
            
            success = false;
            data = {};
            
            try
                response = obj.sendHTTPRequest('GET', '/prs', []);
                
                if ~isempty(response) && response.StatusCode == 200
                    success = true;
                    if ~isempty(response.Body.Data) && isfield(response.Body.Data, 'data')
                        data = response.Body.Data.data;
                    end
                    fprintf('✅ 成功获取PR列表，共%d条记录\n', length(data));
                else
                    fprintf('❌ 查询PR列表失败\n');
                end
                
            catch ME
                fprintf('❌ 查询PR列表异常: %s\n', ME.message);
            end
        end
        
        function [success, data] = queryCRList(obj)
            % 查询CR列表
            % 输出：success - 查询成功标志, data - CR列表数据
            
            success = false;
            data = {};
            
            try
                response = obj.sendHTTPRequest('GET', '/crs', []);
                
                if ~isempty(response) && response.StatusCode == 200
                    success = true;
                    if ~isempty(response.Body.Data) && isfield(response.Body.Data, 'data')
                        data = response.Body.Data.data;
                    end
                    fprintf('✅ 成功获取CR列表，共%d条记录\n', length(data));
                else
                    fprintf('❌ 查询CR列表失败\n');
                end
                
            catch ME
                fprintf('❌ 查询CR列表异常: %s\n', ME.message);
            end
        end
        
        function [success, result] = testConnection(obj)
            % 测试与ALM服务器的连接
            % 输出：success - 连接成功标志, result - 测试结果信息
            
            success = false;
            result = struct();
            
            try
                fprintf('正在测试ALM服务器连接...\n');
                response = obj.sendHTTPRequest('GET', '/health', []);
                
                if ~isempty(response) && response.StatusCode == 200
                    success = true;
                    result.message = '✅ ALM服务器连接正常';
                    result.server = obj.ServerURL;
                    result.testTime = char(datetime('now'));
                    fprintf('%s\n', result.message);
                else
                    result.error = sprintf('❌ ALM服务器连接失败 (HTTP %d)', response.StatusCode);
                    fprintf('%s\n', result.error);
                end
                
            catch ME
                result.error = sprintf('❌ ALM服务器连接异常: %s', ME.message);
                fprintf('%s\n', result.error);
            end
        end
        
        function [success, approvers] = queryApprovers(obj)
            % 查询系统中的审批人列表
            % 输出：
            %   success - 查询是否成功
            %   approvers - 审批人列表（用户名数组）
            
            try
                % 发送GET请求 - 修复：使用正确的endpoint格式
                response = obj.sendHTTPRequest('GET', '/approvers', []);
                
                if response.StatusCode == 200
                    % 解析响应数据
                    responseData = response.Body.Data;
                    
                    if responseData.success
                        % 提取审批人用户名列表
                        approverList = responseData.data.approvers;
                        approvers = cell(length(approverList), 1);
                        
                        for i = 1:length(approverList)
                            approvers{i} = approverList(i).username;
                        end
                        
                        success = true;
                    else
                        success = false;
                        approvers = {};
                        fprintf('查询审批人失败：%s\n', responseData.message);
                    end
                else
                    success = false;
                    approvers = {};
                    fprintf('查询审批人请求失败，状态码：%d\n', response.StatusCode);
                end
                
            catch ME
                success = false;
                approvers = {};
                fprintf('查询审批人异常：%s\n', ME.message);
            end
        end
        
        function [success, userRole] = getUserRole(obj, username)
            % 查询用户角色与权限
            % 输入：username - 用户名（可选，空时查询当前认证用户）
            % 输出：success - 查询成功标志, userRole - 用户角色信息
            
            success = false;
            userRole = struct();
            
            try
                % 构建查询端点
                if nargin < 2 || isempty(username)
                    endpoint = '/user-role';  % 查询当前用户
                else
                    endpoint = sprintf('/user-role?username=%s', username);
                end
                
                fprintf('正在查询用户角色...\n');
                response = obj.sendHTTPRequest('GET', endpoint, []);
                
                if ~isempty(response) && response.StatusCode == 200
                    % 查询成功
                    success = true;
                    if isfield(response.Body.Data, 'data')
                        userRole = response.Body.Data.data;
                        fprintf('✅ 用户角色查询成功: %s (权限: %s)\n', userRole.role, strjoin(userRole.permissions, ', '));
                    end
                    
                else
                    % 查询失败
                    userRole.error = obj.parseErrorResponse(response, '用户角色查询');
                    fprintf('❌ 用户角色查询失败: %s\n', userRole.error);
                end
                
            catch ME
                userRole.error = sprintf('用户角色查询请求失败: %s', ME.message);
                fprintf('❌ %s\n', userRole.error);
            end
        end
        
        function [success, prDetail] = getPRDetail(obj, pr_id)
            % 查询PR详细信息
            % 输入：pr_id - PR ID
            % 输出：success - 查询成功标志, prDetail - PR详细信息
            
            success = false;
            prDetail = struct();
            
            try
                endpoint = sprintf('/pr/%d', pr_id);
                fprintf('正在查询PR %d 详情...\n', pr_id);
                response = obj.sendHTTPRequest('GET', endpoint, []);
                
                if ~isempty(response) && response.StatusCode == 200
                    % 查询成功
                    success = true;
                    if isfield(response.Body.Data, 'data')
                        prDetail = response.Body.Data.data;
                        fprintf('✅ PR详情查询成功: %s\n', prDetail.issueNumber);
                    end
                    
                else
                    % 查询失败
                    prDetail.error = obj.parseErrorResponse(response, 'PR详情查询');
                    fprintf('❌ PR详情查询失败: %s\n', prDetail.error);
                end
                
            catch ME
                prDetail.error = sprintf('PR详情查询请求失败: %s', ME.message);
                fprintf('❌ %s\n', prDetail.error);
            end
        end
        
        function [success, crDetail] = getCRDetail(obj, cr_id)
            % 查询CR详细信息
            % 输入：cr_id - CR ID
            % 输出：success - 查询成功标志, crDetail - CR详细信息
            
            success = false;
            crDetail = struct();
            
            try
                endpoint = sprintf('/cr/%d', cr_id);
                fprintf('正在查询CR %d 详情...\n', cr_id);
                response = obj.sendHTTPRequest('GET', endpoint, []);
                
                if ~isempty(response) && response.StatusCode == 200
                    % 查询成功
                    success = true;
                    if isfield(response.Body.Data, 'data')
                        crDetail = response.Body.Data.data;
                        % CR使用ticketNumber而不是issueNumber
                        fprintf('✅ CR详情查询成功: %s\n', crDetail.ticketNumber);
                    end
                    
                else
                    % 查询失败
                    crDetail.error = obj.parseErrorResponse(response, 'CR详情查询');
                    fprintf('❌ CR详情查询失败: %s\n', crDetail.error);
                end
                
            catch ME
                crDetail.error = sprintf('CR详情查询请求失败: %s', ME.message);
                fprintf('❌ %s\n', crDetail.error);
            end
        end
        
        function [success, data] = getUsers(obj)
            % 获取系统用户清单（需要管理员权限）
            % API接口: GET /users
            % 权限要求: manage_users (仅管理员)
            
            success = false;
            data = {};
            
            try
                fprintf('📋 正在获取系统用户清单（管理员权限）...\n');
                response = obj.sendHTTPRequest('GET', '/users', []);
                
                if ~isempty(response) && response.StatusCode == 200
                    success = true;
                    if ~isempty(response.Body.Data) && isfield(response.Body.Data, 'data')
                        data = response.Body.Data.data;
                        fprintf('✅ 成功获取用户清单，共%d个用户\n', data.total_count);
                    end
                elseif response.StatusCode == 403
                    fprintf('❌ 获取用户清单失败: 权限不足（需要管理员权限）\n');
                else
                    fprintf('❌ 获取用户清单失败 (HTTP %d)\n', response.StatusCode);
                end
                
            catch ME
                fprintf('❌ 获取用户清单异常: %s\n', ME.message);
            end
        end
        
        function [success, data] = getApprovers(obj)
            % 获取审批人清单（任何用户可访问）
            % API接口: GET /approvers
            % 权限要求: 任何认证用户
            
            success = false;
            data = {};
            
            try
                fprintf('👥 正在获取审批人清单...\n');
                response = obj.sendHTTPRequest('GET', '/approvers', []);
                
                if ~isempty(response) && response.StatusCode == 200
                    success = true;
                    if ~isempty(response.Body.Data) && isfield(response.Body.Data, 'data')
                        data = response.Body.Data.data;
                        fprintf('✅ 成功获取审批人清单，共%d个审批人\n', data.total_count);
                    end
                else
                    fprintf('❌ 获取审批人清单失败 (HTTP %d)\n', response.StatusCode);
                end
                
            catch ME
                fprintf('❌ 获取审批人清单异常: %s\n', ME.message);
            end
        end
        
        function [success, projects] = getProjects(obj)
            % 获取项目清单（任何用户可访问）
            % API接口: GET /projects
            % 权限要求: 任何认证用户
            % SRS追溯: F-C-08
            % 
            % 输出：
            %   success - 查询成功标志
            %   projects - 项目名称列表（cell array）
            
            success = false;
            projects = {};
            
            try
                fprintf('📋 正在获取项目清单...\n');
                response = obj.sendHTTPRequest('GET', '/projects', []);
                
                if ~isempty(response) && response.StatusCode == 200
                    success = true;
                    if ~isempty(response.Body.Data) && isfield(response.Body.Data, 'data')
                        data = response.Body.Data.data;
                        if isfield(data, 'projects')
                            projects = data.projects;
                            fprintf('✅ 成功获取项目清单，共%d个项目\n', length(projects));
                        end
                    end
                else
                    fprintf('❌ 获取项目清单失败 (HTTP %d)\n', response.StatusCode);
                end
                
            catch ME
                fprintf('❌ 获取项目清单异常: %s\n', ME.message);
            end
        end
        
        function [success, baselines, projectName] = getBaselines(obj, varargin)
            % 获取基线清单（任何用户可访问）
            % API接口: GET /baselines 或 GET /baselines?project=xxx
            % 权限要求: 任何认证用户
            % SRS追溯: F-C-09
            % 
            % 输入：
            %   project (可选) - 项目名称，用于筛选特定项目的基线
            % 输出：
            %   success - 查询成功标志
            %   baselines - 基线版本列表（cell array）或按项目分组的结构体
            %   projectName - 当前筛选的项目名称（如果提供了project参数）
            
            success = false;
            baselines = {};
            projectName = '';
            
            try
                % 解析输入参数
                if nargin > 1 && ~isempty(varargin{1})
                    projectName = varargin{1};
                    % URL编码项目名称
                    encodedProject = urlencode(projectName);
                    endpoint = sprintf('/baselines?project=%s', encodedProject);
                    fprintf('📋 正在获取项目"%s"的基线清单...\n', projectName);
                else
                    endpoint = '/baselines';
                    fprintf('📋 正在获取所有基线清单...\n');
                end
                
                response = obj.sendHTTPRequest('GET', endpoint, []);
                
                if ~isempty(response) && response.StatusCode == 200
                    success = true;
                    if ~isempty(response.Body.Data) && isfield(response.Body.Data, 'data')
                        data = response.Body.Data.data;
                        
                        if ~isempty(projectName) && isfield(data, 'baselines')
                            % 指定项目的基线列表
                            baselines = data.baselines;
                            fprintf('✅ 成功获取项目"%s"的基线清单，共%d个基线\n', projectName, length(baselines));
                        elseif isfield(data, 'by_project')
                            % 所有项目的基线（按项目分组）
                            baselines = data.by_project;
                            fprintf('✅ 成功获取基线清单，共%d个项目\n', data.total_count);
                        end
                    end
                else
                    fprintf('❌ 获取基线清单失败 (HTTP %d)\n', response.StatusCode);
                end
                
            catch ME
                fprintf('❌ 获取基线清单异常: %s\n', ME.message);
            end
        end
        
        function [success, result] = createUser(obj, userData)
            % 创建新用户（需要管理员权限）
            % API接口: POST /users
            % 权限要求: manage_users (仅管理员)
            
            success = false;
            result = struct();
            
            try
                fprintf('👤 正在创建新用户: %s...\n', userData.username);
                response = obj.sendHTTPRequest('POST', '/users', userData);
                
                if ~isempty(response) && response.StatusCode == 201
                    success = true;
                    result = response.Body.Data.data;
                    fprintf('✅ 用户创建成功!\n');
                elseif response.StatusCode == 409
                    result.error = '用户名已存在';
                    fprintf('❌ 用户创建失败: %s\n', result.error);
                elseif response.StatusCode == 403
                    result.error = '权限不足（需要管理员权限）';
                    fprintf('❌ 用户创建失败: %s\n', result.error);
                else
                    result.error = obj.parseErrorResponse(response, '用户创建');
                    fprintf('❌ 用户创建失败: %s\n', result.error);
                end
                
            catch ME
                result.error = sprintf('用户创建异常: %s', ME.message);
                fprintf('❌ %s\n', result.error);
            end
        end
    end
    
    methods (Access = private)
        function loadConfigFromFile(obj)
            % 从项目配置文件加载ALM服务配置
            % 严格遵循DO-178规则第2条：不使用fallback默认值机制
            
            % 获取项目根目录
            projectRoot = obj.getProjectRoot();
            configPath = fullfile(projectRoot, 'project_configuration', 'project_config.yml');
            
            % 检查配置文件是否存在
            if ~exist(configPath, 'file')
                error('ALMCloudService:ConfigMissing', ...
                    '❌ ALM服务配置文件不存在: %s\n请确保项目配置文件完整', configPath);
            end
            
            try
                % 读取YAML配置文件（使用专业的yamlread工具）
                configData = obj.readYAMLConfig(configPath);
                
                % 检查是否包含alm_service配置节
                if ~isfield(configData, 'alm_service')
                    error('ALMCloudService:ConfigIncomplete', ...
                        '❌ 配置文件中缺少alm_service配置节: %s\n请添加完整的ALM服务配置', configPath);
                end
                
                almConfig = configData.alm_service;
                
                % 验证并设置服务器URL（必需字段）
                if ~isfield(almConfig, 'server_url') || isempty(almConfig.server_url)
                    error('ALMCloudService:ConfigIncomplete', ...
                        '❌ 配置文件中缺少server_url字段或值为空\n请在alm_service节中配置server_url');
                end
                obj.ServerURL = almConfig.server_url;
                
                % 验证并设置API密钥（必需字段）
                if ~isfield(almConfig, 'api_key') || isempty(almConfig.api_key)
                    error('ALMCloudService:ConfigIncomplete', ...
                        '❌ 配置文件中缺少api_key字段或值为空\n请在alm_service节中配置api_key');
                end
                obj.APIKey = almConfig.api_key;
                
                % 验证并设置超时时间（可选字段，有默认值）
                if isfield(almConfig, 'timeout_seconds') && ~isempty(almConfig.timeout_seconds)
                    if ~isnumeric(almConfig.timeout_seconds) || almConfig.timeout_seconds <= 0
                        error('ALMCloudService:ConfigInvalid', ...
                            '❌ 配置文件中timeout_seconds必须为正数');
                    end
                    obj.TimeoutSeconds = almConfig.timeout_seconds;
                else
                    obj.TimeoutSeconds = 30;  % 唯一允许的默认值
                end
                
                fprintf('✅ 从配置文件加载ALM服务配置: %s\n', configPath);
                fprintf('   服务器: %s\n', obj.ServerURL);
                fprintf('   超时时间: %d秒\n', obj.TimeoutSeconds);
                
            catch ME
                if strcmp(ME.identifier, 'ALMCloudService:ConfigMissing') || ...
                   strcmp(ME.identifier, 'ALMCloudService:ConfigIncomplete') || ...
                   strcmp(ME.identifier, 'ALMCloudService:ConfigInvalid')
                    % 重新抛出已知的配置错误
                    rethrow(ME);
                else
                    % 包装未知的配置解析错误
                    error('ALMCloudService:ConfigParseError', ...
                        '❌ 解析配置文件失败: %s\n原因: %s', configPath, ME.message);
                end
            end
        end
        
        function projectRoot = getProjectRoot(obj)
            % 获取项目根目录
            % 从当前文件位置向上查找包含project_configuration目录的路径
            currentPath = fileparts(mfilename('fullpath'));
            
            % 向上查找项目根目录
            while ~isempty(currentPath)
                configDir = fullfile(currentPath, 'project_configuration');
                if exist(configDir, 'dir')
                    projectRoot = currentPath;
                    return;
                end
                
                parentPath = fileparts(currentPath);
                if strcmp(parentPath, currentPath)
                    break;  % 已到达根目录
                end
                currentPath = parentPath;
            end
            
            % 如果找不到，使用当前工作目录
            projectRoot = pwd;
        end
        
        function configData = readYAMLConfig(obj, configPath)
            % 使用专业的yamlread工具读取YAML配置文件
            % 依赖：tools/utilities/yamltool/yamlread.m
            
            try
                % 使用专业的yamlread函数解析配置文件
                fprintf('🔍 使用专业YAML工具解析配置文件: %s\n', configPath);
                configData = yamlread(configPath);
                
                if isempty(configData)
                    error('ALMCloudService:ConfigEmpty', '❌ 配置文件为空或格式无效');
                end
                
                fprintf('✅ YAML配置文件解析成功\n');
                
                
            catch ME
                
                if strcmp(ME.identifier, 'ALMCloudService:YamlToolMissing') || ...
                   strcmp(ME.identifier, 'ALMCloudService:ConfigEmpty')
                    rethrow(ME);
                else
                    error('ALMCloudService:YamlParseError', ...
                        '❌ YAML配置文件解析失败: %s\n原因: %s\n请检查YAML语法是否正确', ...
                        configPath, ME.message);
                end
            end
        end
        function response = sendHTTPRequest(obj, method, endpoint, data)
            % 发送HTTP请求的核心方法
            
            import matlab.net.*
            import matlab.net.http.*
            
            % 构建完整URL
            url = [obj.ServerURL, endpoint];
            
            % 设置请求头
            header = [HeaderField('Content-Type', 'application/json'), ...
                      HeaderField('X-API-KEY', obj.APIKey)];
            
            % 构建请求
            if nargin > 3 && ~isempty(data)
                json_data = obj.convertStructToJSON(data);
                body = MessageBody(json_data);
                request = RequestMessage(method, header, body);
            else
                request = RequestMessage(method, header);
            end
            
            % 设置超时
            options = HTTPOptions('ConnectTimeout', obj.TimeoutSeconds, ...
                                  'ResponseTimeout', obj.TimeoutSeconds);
            
            % 发送请求
            fprintf('发送HTTP请求: %s %s\n', char(method), url);
            response = send(request, URI(url), options);
            fprintf('服务器响应: %d\n', response.StatusCode);
            
            % 调试输出
            if ~isempty(response.Body.Data)
                fprintf('响应数据: %s\n', jsonencode(response.Body.Data, 'PrettyPrint', true));
            end
        end
        
        function serverData = convertToServerFormat(obj, inputData)
            % 将GUI输入数据转换为ALM服务器格式 - API v2.0规格（15个字段）
            % 严格遵循DO-178规则第2条，不提供fallback默认值
            serverData = struct();
            
            % ========== PR必填字段（11个） - API v2.0 ==========
            % 基础信息（5个）
            if isfield(inputData, 'project')
                serverData.project = inputData.project;
            end
            
            if isfield(inputData, 'issueNumber')
                serverData.issueNumber = inputData.issueNumber;
            end
            
            if isfield(inputData, 'reporter')
                serverData.reporter = inputData.reporter;
            end
            
            if isfield(inputData, 'source')
                serverData.source = inputData.source;  % 字符串，无枚举限制
            end
            
            if isfield(inputData, 'initialSeverity')
                serverData.initialSeverity = inputData.initialSeverity;  % 枚举：严重/一般
            end
            
            % 问题描述和分析（4个）- 多行文本字段
            if isfield(inputData, 'problemDescription')
                serverData.problemDescription = inputData.problemDescription;
            end
            
            if isfield(inputData, 'problemMechanism')
                serverData.problemMechanism = inputData.problemMechanism;
            end
            
            if isfield(inputData, 'relatedRequirements')
                serverData.relatedRequirements = inputData.relatedRequirements;
            end
            
            if isfield(inputData, 'impactAnalysis')
                serverData.impactAnalysis = inputData.impactAnalysis;
            end
            
            % 分配和审批（2个）
            if isfield(inputData, 'assignee')
                serverData.assignee = inputData.assignee;
            end
            
            if isfield(inputData, 'approver')
                serverData.approver = inputData.approver;
            end
            
            % ========== PR可选字段（4个） - API v2.0 ==========
            if isfield(inputData, 'baseline')
                serverData.baseline = inputData.baseline;
            end
            
            if isfield(inputData, 'verificationStatus')
                serverData.verificationStatus = inputData.verificationStatus;
            end
            
            if isfield(inputData, 'confirmedSeverity')
                serverData.confirmedSeverity = inputData.confirmedSeverity;
            end
            
            if isfield(inputData, 'disposalPlan')
                serverData.disposalPlan = inputData.disposalPlan;
            end
            
            % ========== CR字段支持 ==========
            % CR必填字段（4个）
            if isfield(inputData, 'ticketNumber')
                serverData.ticketNumber = inputData.ticketNumber;
            end
            
            if isfield(inputData, 'changeDescription')
                serverData.changeDescription = inputData.changeDescription;
            end
            
            % CR可选字段
            if isfield(inputData, 'reportDate')
                serverData.reportDate = inputData.reportDate;
            end
            
            if isfield(inputData, 'changeSource')
                serverData.changeSource = inputData.changeSource;
            end
            
            if isfield(inputData, 'analysis')
                serverData.analysis = inputData.analysis;
            end
            
            if isfield(inputData, 'related_pr_id')
                serverData.related_pr_id = inputData.related_pr_id;
            end
            
            % ⚠️ 重要：创建PR/CR时禁止包含审批相关字段
            % approval, approvalComment, approvalTime 由系统自动处理
            % 这些字段只能通过专门的审批接口设置
            % 违反此限制将返回错误码 PR_017/CR_017
        end
        
        function cellArray = ensureCellArray(obj, input)
            % 确保输入转换为cell数组格式
            if iscell(input)
                cellArray = input;
            elseif isstring(input) || ischar(input)
                if isempty(input) || (ischar(input) && isempty(strtrim(input)))
                    cellArray = {''};
                else
                    cellArray = {char(input)};
                end
            else
                cellArray = {''};
            end
        end
        
        function json_str = convertStructToJSON(obj, data)
            % 将MATLAB结构体转换为JSON字符串
            try
                json_str = jsonencode(data, 'PrettyPrint', false);
            catch ME
                fprintf('警告：JSON转换异常，使用基本方法: %s\n', ME.message);
                json_str = jsonencode(data);
            end
        end
        
        function isValid = validatePRData(obj, prData)
            % 验证PR数据的完整性 - API v2.0规格（11个必填字段）
            % 基于DO-178规则第2条：明确错误处理，不使用fallback
            isValid = true;
            
            % ========== 必填字段验证（11个） - API v2.0 ==========
            % 基础信息（5个）
            requiredFields = {
                'project', 'issueNumber', 'reporter', 'source', 'initialSeverity', ...
                'problemDescription', 'problemMechanism', 'relatedRequirements', ...
                'impactAnalysis', 'assignee', 'approver'
            };
            
            for i = 1:length(requiredFields)
                field = requiredFields{i};
                if ~isfield(prData, field) || isempty(prData.(field))
                    fprintf('PR验证失败：缺少必填字段 %s\n', field);
                    isValid = false;
                end
            end
            
            % 验证枚举字段（initialSeverity）
            if isfield(prData, 'initialSeverity')
                validSeverities = {'严重', '一般'};
                if ~any(strcmp(prData.initialSeverity, validSeverities))
                    fprintf('PR验证失败：initialSeverity必须是"严重"或"一般"之一\n');
                    isValid = false;
                end
            end
            
            % 验证可选枚举字段（confirmedSeverity）
            if isfield(prData, 'confirmedSeverity') && ~isempty(prData.confirmedSeverity)
                validSeverities = {'严重', '一般'};
                if ~any(strcmp(prData.confirmedSeverity, validSeverities))
                    fprintf('PR验证失败：confirmedSeverity必须是"严重"或"一般"之一\n');
                    isValid = false;
                end
            end
            
            % ⚠️ 验证禁止字段：确保创建时不包含审批相关字段
            prohibitedFields = {'approval', 'approvalComment', 'approvalTime'};
            for i = 1:length(prohibitedFields)
                field = prohibitedFields{i};
                if isfield(prData, field)
                    fprintf('PR验证失败：创建时禁止包含字段 %s（将返回错误码 PR_017）\n', field);
                    isValid = false;
                end
            end
            
            % 验证字段长度限制
            if isfield(prData, 'project') && length(prData.project) > 200
                fprintf('PR验证失败：project长度不能超过200字符\n');
                isValid = false;
            end
            
            if isfield(prData, 'issueNumber') && length(prData.issueNumber) > 100
                fprintf('PR验证失败：issueNumber长度不能超过100字符\n');
                isValid = false;
            end
            
            if isfield(prData, 'baseline') && length(prData.baseline) > 100
                fprintf('PR验证失败：baseline长度不能超过100字符\n');
                isValid = false;
            end
            
            if isfield(prData, 'assignee') && length(prData.assignee) > 100
                fprintf('PR验证失败：assignee长度不能超过100字符\n');
                isValid = false;
            end
            
            % approvalComment字段已移除，不再验证长度
        end
        
        function isValid = validateCRData(obj, crData)
            % 验证CR数据的完整性 - API v2.0规格
            % 基于DO-178规则第2条：明确错误处理，不使用fallback机制
            % 
            % 必填字段（4个）：ticketNumber, reporter, approver, changeDescription
            % 可选字段（7个）：project, assignee, reportDate, changeSource, baseline, analysis, related_pr_id
            isValid = true;
            
            % ========== 检查必填字段（4个） ==========
            requiredFields = {'ticketNumber', 'reporter', 'approver', 'changeDescription'};
            for i = 1:length(requiredFields)
                field = requiredFields{i};
                if ~isfield(crData, field) || isempty(crData.(field))
                    fprintf('❌ CR验证失败：缺少必填字段 %s\n', field);
                    isValid = false;
                end
            end
            
            % ========== 验证changeDescription的数据类型 ==========
            % changeDescription应该是string类型（多行文本）
            if isfield(crData, 'changeDescription')
                % 允许string或char类型
                if ~ischar(crData.changeDescription) && ~isstring(crData.changeDescription)
                    fprintf('❌ CR验证失败：changeDescription 必须是字符串类型（多行文本）\n');
                    isValid = false;
                end
            end
            
            % ========== 验证可选字段格式 ==========
            
            % 检查reportDate格式（如果存在，应为YYYY-MM-DD格式）
            if isfield(crData, 'reportDate') && ~isempty(crData.reportDate)
                try
                    datetime(crData.reportDate, 'InputFormat', 'yyyy-MM-dd');
                catch
                    fprintf('❌ CR验证失败：reportDate 必须为 YYYY-MM-DD 格式，当前值: %s\n', crData.reportDate);
                    isValid = false;
                end
            end
            
            % 检查related_pr_id格式（如果存在，应为正整数）
            if isfield(crData, 'related_pr_id') && ~isempty(crData.related_pr_id)
                if ~isnumeric(crData.related_pr_id) || crData.related_pr_id <= 0 || mod(crData.related_pr_id, 1) ~= 0
                    fprintf('❌ CR验证失败：related_pr_id 必须为正整数，当前值: %s\n', string(crData.related_pr_id));
                    isValid = false;
                end
            end
            
            % ⚠️ 重要：根据API规格，创建CR时禁止包含以下字段
            % approval, approvalComment, approvalTime - 系统自动处理
            forbiddenFields = {'approval', 'approvalComment', 'approvalTime', 'status'};
            for i = 1:length(forbiddenFields)
                field = forbiddenFields{i};
                if isfield(crData, field)
                    fprintf('❌ CR验证失败：创建CR时禁止包含字段 %s（系统自动处理）\n', field);
                    isValid = false;
                end
            end
            
            if isValid
                fprintf('✅ CR数据验证通过\n');
            end
        end
        
        function errorMsg = parseErrorResponse(obj, response, operationType)
            % 解析服务器错误响应
            % 输入：
            %   response - HTTP响应对象
            %   operationType - 操作类型 ('PR' 或 'CR')，默认为 'PR'
            
            if nargin < 3 || isempty(operationType)
                operationType = 'PR';
            end
            
            if isempty(response)
                errorMsg = sprintf('%s提交失败：无法连接到服务器', operationType);
            else
                errorMsg = sprintf('%s提交失败：HTTP %d', operationType, response.StatusCode);
                if ~isempty(response.Body) && ~isempty(response.Body.Data)
                    if isfield(response.Body.Data, 'message')
                        errorMsg = sprintf('%s - %s', errorMsg, response.Body.Data.message);
                    else
                        errorMsg = sprintf('%s - %s', errorMsg, jsonencode(response.Body.Data));
                    end
                end
            end
        end
        
        function debugLog(obj, varargin)
            % 调试日志输出，仅在DebugMode为true时输出
            % 用法：obj.debugLog('格式字符串', 参数1, 参数2, ...)
            if obj.DebugMode
                fprintf(varargin{:});
            end
        end
    end
end
